package net.bithappens.hnqe.impl.mapper;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import net.bithappens.hnqe.cache.ICacheManipulate;
import net.bithappens.hnqe.cache.ICacheQuery;
import net.bithappens.hnqe.mapper.IMapper;
import net.bithappens.hnqe.mapper.IMapperPool;

import org.apache.log4j.Logger;

public class MapperPool implements IMapperPool {

	private Logger log = Logger.getLogger(this.getClass());

	private String mapperDirPath;

	private MapperContainer mapperContainer = new MapperContainer();

	private ICacheManipulate cache;

	public MapperPool(ICacheManipulate cache) throws IOException {

		Properties properties = new Properties();
		URL url = ClassLoader.getSystemResource("hnqe.properties");
		InputStream propertyStream = url.openStream();
		properties.load(propertyStream);
		propertyStream.close();

		mapperDirPath = properties.getProperty("hnqe.mapper.path");

		this.cache = cache;
	}

	@Override
	public void loadMapper() throws IOException {

		File mapperDir = new File(mapperDirPath);

		if (mapperDir.exists()) {

			if (mapperDir.isDirectory()) {

				ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

				addMapperDirToClassPath(classLoader, mapperDir);

				for (File file : mapperDir.listFiles()) {
					monitorFile(file, classLoader);
				}

			} else {
				throw new IOException("the given path is no directory");
			}
		} else {
			throw new IOException("the given path doesn't exist");
		}
	}

	private void addMapperDirToClassPath(ClassLoader classLoader, File folder) throws IOException {
		
		if (classLoader instanceof URLClassLoader) {
			
			try {
				
				File[] files = folder.listFiles();

				Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
				
				addUrlMethod.setAccessible(true);
				
				if (addUrlMethod != null) {
					
					for (int i = 0; i < files.length; i++) {
						addUrlMethod.invoke((Object) classLoader, new Object[] { files[i].toURI().toURL() });
					}

				}

			} catch (Exception e) {
				throw new IOException("One or more of the resource provided in the mapper folder couldn't be loaded");
			}
		}
	}

	private void monitorFile(File file, ClassLoader classLoader) {
		if (file.getName().toLowerCase().endsWith(".jar")) {

			try {
				JarFile jarFile = new JarFile(file, false, JarFile.OPEN_READ);
				Enumeration<JarEntry> jarEntryEnumerator = jarFile.entries();

				while (jarEntryEnumerator.hasMoreElements()) {

					String entryName = jarEntryEnumerator.nextElement().getName();

					if (entryName.endsWith(".class")) {

						String absoluteClassName = entryName.substring(0, entryName.indexOf(".class"))
								.replace("/", ".");

						Class<?> classItem = classLoader.loadClass(absoluteClassName);

						for (Class<?> interfaceClass : classItem.getInterfaces()) {
							if (interfaceClass.equals(IMapper.class)) {
								IMapper mapper = (IMapper) classItem.newInstance();
								if (!mapperContainer.containsMapper(mapper)) {
									mapper.setCache(cache);
									mapperContainer.addMapper(mapper);
								}
							}
						}
					}
				}

				jarFile.close();
			} catch (Exception ex) {
				System.err.println("Couldn't read jar " + file.getName());
				ex.printStackTrace();
			}
		}
	}

	@Override
	public Collection<IMapper> getAllMapper(String instanceId) {
		return mapperContainer.getMappersOfInstance(instanceId);
	}

	@Override
	public void startMapper() {
		for (IMapper mapper : mapperContainer.getAllMappers()) {
			mapper.start();
		}
	}

	@Override
	public void stopMapper() {
		for (IMapper mapper : mapperContainer.getAllMappers()) {
			mapper.stop();
		}
	}
}
